from util import *
from Request import *
import simpy

class MicroService:
    def __init__(self, cloud_system, node_obj, ms_kind, ms_id, without_cold_start):
        #云环境
        self.cloud_system=cloud_system
        #部署的Node
        self.node_obj=node_obj
        #simpy环境
        self.env =cloud_system.env
        #需要的cpu资源
        self.cpu_need=cloud_system.ms_kind_to_resource_need[ms_kind]["cpu"]
        #需要的mem资源
        self.mem_need=cloud_system.ms_kind_to_resource_need[ms_kind]["mem"]
        #需要的disk资源
        self.disk_need=cloud_system.ms_kind_to_resource_need[ms_kind]["disk"]
        #输出水平
        self.print_level=cloud_system.print_level
        #调度器
        self.scheduler=cloud_system.scheduler
        #微服务类型
        self.ms_kind =ms_kind
        #微服务ID，独一无二
        self.ms_id =ms_id
        #微服务状态转换平均时长的倒数，即泊松分布的期望，指数分布期望的倒数
        self.lambda_transient_ms=cloud_system.ms_kind_to_transient_lambda[ms_kind]
        
        #微服务任务请求队列
        self.queue =[]
        self.on_way_queue=[]
        self.blocking_queue=[]
        # 排队强制无法插队的时间
        self.no_cut_time_in_queue=cloud_system.args.no_cut_time_in_queue
        #微服务状态
        self.state = "starting"
        # 记录首次状态持续时间（系统刚开始时，默认微服务处于正常运行状态）
        if without_cold_start:
            self.cold_start_time=0
            self.cold_start_do_task=False 
        else:
            #毫秒
            self.cold_start_time = cloud_system.get_cold_start_time(ms_kind)   
            self.cold_start_do_task=True 
        self.end_cold_start_time=None
        
        #需要改变的微服务特征属性
        self.start_time =self.env.now
        self.start_deal_task_time=None
        self.end_deal_task_time=None
        self.end_time=None
        #任务开始处理时间
        # self.working_start_time = 0
        self.acc_working_time=0
        self.acc_exist_time=0
        self.acc_idle_time=0
        #微服务是否在忙
        self.isbusy = False
        #记录阻塞事件，用于辅助判断系统是否结束和isbusy配合使用
        self.block_event_num=0
        #task正在传输过来的数量
        self.task_num_on_the_way=0
        #微服务准备被删除
        self.ready_to_rm=False
        #资源消耗信息（单位时间消耗）
        self.call_cost = self.cloud_system.ms_kind_to_call_cost[ms_kind]
        self.record_dealed_task_num=0
        #记录完成的任务数量，包括失败和成功
        self.done_task_num=0
        #记录失败的任务数量
        self.error_task_num=0
        #需要改变的任务特征属性
        #当前正在处理的任务
        self.current_task = None
        #开始微服务状态转换的进程
        self.state_transition_process=self.env.process(self._ms_state_transition())
        self.stable_flage_for_ours=False
        
        if self.print_level>20:
            print(f"Time:{round(self.env.now)}\t (MS INIT EVENT) \t{self.ms_id} in node:{self.node_obj.node_id} init ... (without cold start:{without_cold_start})")
            
            
        # self.recoverable_failure_event=self.env.process(self._ms_recoverable_state_transition())
        # self.task_deal_event = None
        #our method 记录数据
        # self.env.process(self.clean_record_do(init_flage=True))
        return
    
    
    def put_task_to_queue_immediately(self, task):
        task.record_arrive_queue_time_point(self.ms_id, self.env.now)
        insert_location=0
        #需要检查
        if self.cloud_system.args.priority_queue==True:
            #part 感知，对task进行排序默认1000毫秒的不能被插队
            time_now=self.env.now
            for index in range(len(self.queue)-1, -1, -1):
                task_t=self.queue[index]
                assert isinstance(task.part_id, (int, float))
                assert isinstance(task_t.part_id, (int, float))
                if task.part_id>0 or task_t.part_id==0 or time_now-task_t.real_arrive_queue_time_point[self.ms_id]>self.no_cut_time_in_queue:
                    self.queue.insert(index+1, task)
                    insert_location=index+1
                    break
            else:
                self.queue.insert(0, task) 
                insert_location=0
        else:
            insert_location=len(self.queue)
            self.queue.append(task)
            #记录冷启动时间
            # if self.state=="starting" and insert_location==0:
            #     task.request.sum_cold_start_time+=self.cold_start_time
            #     self.cold_start_task_id=task.task_id
                
            
            
        if self.print_level>10:
            print(f"Time:{round(self.env.now)}\t (QUEUE ARRIVE EVENT) \t{task.task_id}{task.request.request_id}| arrive MS:{self.ms_id} queue len:{len(self.queue)} (IL:{insert_location})")       
            
        if not self.isbusy and self.state=="normal":
            self.env.process(self.deal_task())
            
    def put_task_to_queue_event(self, task):
        delay_time=self.cloud_system.get_TT_time(task, self, task.first_parent_ms_obj)
        self.task_num_on_the_way+=1
        self.on_way_queue.append(task)
        yield self.env.timeout(delay_time)
        #记录传输时间
        task.request.sum_transform_time+=delay_time
        
        self.task_num_on_the_way-=1
        self.on_way_queue.remove(task)
        self.put_task_to_queue_immediately(task)
    
    #处理任务
    def deal_task(self):
        # # 记录当前开始工作时间
        # self.working_start_time = self.env.now
        #记录空闲时间
        if self.isbusy==False and self.block_event_num==0:
            if self.start_deal_task_time!=None:  #创建后首次使用不需要记录空闲时间
                self.update_idle_time()
            self.start_deal_task_time=self.env.now
            self.end_deal_task_time=None
        self.isbusy=True
        while True:
            if self.current_task == None and len(self.queue) == 0:
                # self.cloud_system.judge_system_end()
                self.isbusy=False
                #当前没有任务后，尝试看看系统队列里是否有未完成的任务，启动调度
                self.env.process(self.cloud_system.scheduler.do_schedule_deploy_event())
                
                break
             
                # if self.ready_to_rm:
                #     self.node_obj.remove_ms(self.ms_id)
                # else:
                    
                
            
            elif self.current_task == None:
                self.current_task=self.queue.pop(0)
                
                if self.cold_start_do_task==True:
                    # assert self.cold_start_task_id==self.current_task.task_id
                    self.current_task.request.cold_start_times+=1
                    self.cold_start_do_task=False
                
                if self.cold_start_time==0:
                     self.current_task.request.hit_pre_deploy_times+=1
                #统计冷启动时间
                
                arrive_queue_time=self.current_task.get_arrive_queue_time_point(self.ms_id)
                self.current_task.request.sum_cold_start_time+=max(0, self.end_cold_start_time-arrive_queue_time)
                self.current_task.request.sum_wait_time+=max(0,self.env.now-max(self.end_cold_start_time,arrive_queue_time))
                # self.current_task.record_for_start_deal(self.ms_id, self.env.now)
                
                if self.print_level>10:
                    print(f"Time:{round(self.env.now)}\t (START   EVENT) \t{self.current_task.task_id}{self.current_task.request.request_id}| in {self.ms_id}, time: {self.current_task.deal_time} subtask:",end="")
                    for task in self.current_task.sub_task_list:
                        print(f" {task.task_id}({task.rpc_type}),",end="")
                    print("")
            
            #生成下一级任务，并等待一段时间然后进行调度
            self.generate_next_level_task_record()
            #更新已知的子任务数量信息
            # self.update_sub_task_info(self.current_task)
            
            #处理过程，时间消耗
            yield self.env.timeout(self.current_task.deal_time)
            #统计处理时间，并记录处理完成时间
            self.current_task.request.sum_deal_time+=self.current_task.deal_time
            
            
            self.env.process(self.blocking_event(self.current_task))
            self.current_task=None 
        return
    
    
    def generate_next_level_task_record(self):
        if self.current_task.generate_next_tasks_flage==False:
            self.current_task.generate_next_tasks_flage=True
            for next_task in self.current_task.sub_task_list:
                next_task.first_parent_ms_obj=self
                # if next_task.rpc_type!="mq":
                #     next_task.part_id=self.current_task.part_id
                # else:
                #     next_task.part_id=self.current_task.request.part_order
                #     self.current_task.request.part_order+=1
                    
                self.env.process(self.waiting_for_scheduling(next_task.pre_time, next_task))
        return
    
    def update_sub_task_info(self, task):
        if self.cloud_system.args.deploy_schedule_strategy=="ours" and task.update_base_info_flage==False and self.cloud_system.args.dynamic_replica==True:
            task.request.request_base.update_base_info(task)
            task.update_base_info_flage=True
    
    def blocking_event(self, task):
        self.block_event_num+=1
        start_block_time=self.env.now
        if task.need_back_task_num>0:
            # max_wait=self.env.timeout(1000,value="time out")
            yield task.can_back_event           #| max_wait #back true or time out
            # if ret=={max_wait:"time out"}:
            #     task.request.set_task_response_timeout()
            #     print(f"Time:{round(self.env.now)}\t (TIMEOUT EVENT) \t{task.task_id}{task.request.request_id}| in {self.ms_id}!")

        #任务完成
        if self.print_level>10:
                print(f"Time:{round(self.env.now)}\t (END     EVENT) \t{task.task_id}{task.request.request_id}| in {self.ms_id}")
        #当前任务完成后的操作
        task.end_and_back(self.ms_id, self.env.now)
        #记录等待时间
        task.request.sum_wait_time+=self.env.now-start_block_time
        
        # self.record_end_task(task)
        self.block_event_num-=1
        #记录开始空闲时间
        if self.block_event_num==0 and self.isbusy==False:
            self.update_working_time()
            self.start_deal_task_time=None
            self.end_deal_task_time=self.env.now
        
            
        if self.cloud_system.judge_system_end():
            self.env.process(self.cloud_system.end_event())
        
        #判断当前微服务是否需要移除
        if self.block_event_num==0 and self.isbusy==False and self.ready_to_rm==True:
            self.node_obj.remove_ms(self.ms_id)
        
        if self.stable_flage_for_ours==False and self.can_be_rm():
            self.node_obj.cloud_system.scheduler.ms_complete_and_can_be_rm(self)
    
    
    # 处理过程中，调用其他微服务
    def waiting_for_scheduling(self, time_delay, task_f):
        # print(f"Time:{self.env.now} delay time:{time_delay} come task{task_f.task_id}")
        yield self.env.timeout(time_delay)
        if self.print_level>10:
            print(f"Time:{round(self.env.now)}\t (TASK COME EVENT) \t{task_f.task_id}{task_f.request.request_id}| come ...")
        self.cloud_system.add_one_task(task_f)
        
        return
    
    
    def update_working_time(self):
        statistic_start_time=self.node_obj.cloud_system.resource_statistic_start_time
        statistic_end_time=self.node_obj.cloud_system.resource_statistic_end_time
        if statistic_start_time==None:
            return
        
        if statistic_end_time==None:
            self.acc_working_time=self.acc_working_time+(self.env.now-max(self.start_deal_task_time, statistic_start_time))
        else:
            self.acc_working_time=self.acc_working_time+max(0,(min(self.env.now, statistic_end_time)-max(self.start_deal_task_time, statistic_start_time)))
        
    def update_idle_time(self):
        statistic_start_time=self.node_obj.cloud_system.resource_statistic_start_time
        statistic_end_time=self.node_obj.cloud_system.resource_statistic_end_time
        if statistic_start_time==None or self.end_deal_task_time==None:
            return
        if statistic_end_time==None:
            self.acc_idle_time=self.acc_idle_time+(self.env.now-max(self.end_deal_task_time,statistic_start_time))
        else:
            self.acc_idle_time=self.acc_idle_time+max(0, (min(self.env.now, statistic_end_time)-max(self.end_deal_task_time,statistic_start_time)))
            
    def update_exist_time(self):
        statistic_start_time=self.node_obj.cloud_system.resource_statistic_start_time
        statistic_end_time=self.node_obj.cloud_system.resource_statistic_end_time
        if statistic_start_time==None:
            return
        if statistic_end_time==None:
            self.acc_exist_time=self.env.now-max(self.start_time, statistic_start_time)
        else:
            self.acc_exist_time=max(0, min(self.env.now, statistic_end_time)-max(self.start_time, statistic_start_time))
        
    #微服务状态转换函数
    def _ms_state_transition(self):
        try:
            yield self.env.timeout(self.cold_start_time)
            self.state="normal"
            self.end_cold_start_time=self.env.now
            if not self.isbusy and len(self.queue)>0:
                self.env.process(self.deal_task())
            change_time=round(get_time_with_exponent(self.lambda_transient_ms))
            while self.cloud_system.system_end==False:
                yield self.env.timeout(change_time)
                if self.print_level >20:
                    print(f"********time:{self.env.now}\tMS:{self.ms_id}\ttransient failure ")
                # 发生故障后，将当前任务设置为失败
                if self.current_task is not None:
                    self.current_task.task_with_error()
                change_time = round(get_time_with_exponent(self.lambda_transient_ms))
        except simpy.Interrupt:
            if self.print_level >20:
                print(f"{self.ms_id} 状态转换中断！")
    
    def judge_ms_idle(self):
        
        if len(self.queue)==0 and self.current_task==None and self.block_event_num==0:
            return True
        else:
            return False
   
    def get_queue_len(self):
        return len(self.queue)
    
    def get_cost_by_work(self):
        #按小时计费，所以除以3600000
        call_cost=self.acc_working_time*self.call_cost/3600000
        return call_cost

    def get_cost_by_deploy(self):
        #按小时计费，所以除以3600000
        cost=(self.env.now-self.start_time)*self.call_cost/3600000
        
        return cost
    
    
    def get_current_resource_usage(self):
        S_time=self.node_obj.cloud_system.resource_statistic_start_time
        E_time=self.node_obj.cloud_system.resource_statistic_end_time
        #
        if S_time==None:
            return 0,0,0
        
        if E_time==None:
            time_length=max(0, self.env.now-max(self.start_time,S_time))/1000
        else:
            time_length=max(0, min(self.env.now, E_time)-max(self.start_time, S_time))/1000
        
        # if self.node_obj.cloud_system.resource_statistic_end_time!=None and self.start_time>self.node_obj.cloud_system.resource_statistic_end_time:
        #     return 0,0,0
        
        
        
        # if self.node_obj.cloud_system.resource_statistic_end_time==None:
        #     resource_statistic_end_time_t=self.env.now
        # else:
        #     resource_statistic_end_time_t=min(self.env.now, self.node_obj.cloud_system.resource_statistic_end_time)
        # time_length=(resource_statistic_end_time_t-self.start_time)/1000
        return self.cpu_need*time_length, self.mem_need*time_length, self.disk_need*time_length
    
    def get_deadline_violation(self):
        return self.record_violation_task_num/self.record_dealed_task_num if self.record_dealed_task_num>0 else 0
    
    

    def prepare_to_rm(self):
        self.ready_to_rm=True
        
    def is_idle(self):
        return (not self.isbusy ) and (not self.ready_to_rm)
    
    def can_be_rm(self):
        if len(self.queue)==0 and self.current_task==None and self.task_num_on_the_way==0 and self.block_event_num==0:
            return True
        else:
            return False
    
    def is_available_for_task(self):
        return not self.ready_to_rm
    
    def remove(self):
        self.update_idle_time()
        self.update_exist_time()
        self.state="remove"
        self.state_transition_process.interrupt()
        self.end_time=self.env.now
        
    
    
    #获取队列中task的禁止的ms id以防止死锁
    def get_suc_task_prohibit_ms_id(self):
        suc_task_prohibit_ms_id=[]
        for task in self.queue:
            suc_task_prohibit_ms_id.extend(task.prohibit_ms_id_list)
        return suc_task_prohibit_ms_id
    
    #根据当前任务队列情况，和历史处理时长，评估最终的完成时间
    def get_estimate_end_time(self):
        extend_time=0
        if self.current_task!=None:
            if self.current_task.ave_deal_time<(self.env.now-self.start_deal_task_time):
                extend_time=0 #float("inf")
            else:
                extend_time+=self.current_task.ave_deal_time-(self.env.now-self.start_deal_task_time)
        for task_t in self.queue:
            extend_time+=task_t.ave_deal_time
        for task_t in self.on_way_queue:
            extend_time+=task_t.ave_deal_time
            
        return self.env.now+extend_time
    
    # def get_current_resource_usage(self):
        
    #     time=self.env.now-self.start_time
    #     return time*(self.cpu_need+self.mem_need+self.disk_need)
        
        
        
    # def clean_record_do(self, init_flage=False):
    #     if not init_flage:
    #         yield self.env.timeout(self.cold_start_time)
    #     #待添加需要清空的记录指标
    #     self.record_start_time=self.env.now
    #     self.record_dealed_task_num=0
    #     self.record_violation_task_num=0
        
    #     self.record_sum_waiting_time=0
    #     self.record_sum_deal_time=0
    #     self.record_dealing_time=0
    
    
    
    
    # def get_idle_time(self):
    #     return 1-(self.record_dealing_time/(self.env.now-self.record_start_time))
    
    # def get_idle_percent(self):
    #     return 1-(self.record_dealing_time/(self.env.now-self.start_time)) if self.env.now-self.start_time!=0 else 1
        

    
    # def record_end_task(self, task):
    #     if self.cloud_system.args.deploy_schedule_strategy=="ours":
    #         self.record_dealed_task_num+=1
    #         # sub_deadline=task.request.request_base.task_id_to_obj[task.task_id].sub_deadline
    #         # if self.env.now>task.request.arrive_time+sub_deadline:
    #         #     self.record_violation_task_num+=1
    #         self.record_sum_waiting_time+=task.real_waiting_time[self.ms_id]
    #         self.record_sum_deal_time+=task.real_deal_time[self.ms_id]
    #         self.record_dealing_time+=self.env.now-self.start_deal_task_time
    #     return
    
    
    
    
    # def set_plan_idle_time(self, next_idle_time):
    #     self.next_idel_time=next_idle_time
    
        
    # def get_plan_idle_time(self):
        
    #     return self.next_idel_time if self.cloud_system.env.now<self.next_idel_time else self.cloud_system.env.now
    
    
    # def clean_record(self):
    #     if self.cloud_system.args.deploy_schedule_strategy=="ours":
    #         #需要等待冷启动结束后，再清理，因为冷启动结束后的结果才体现当前部署方案的影响
    #         self.env.process(self.clean_record_do())
    
    
    
    # def get_key_info(self):
    #     if self.record_dealed_task_num==0:
    #         ave_waiting_time=0
    #         ave_complet_time=0
    #         deadline_violation=0
    #     else:
    #         ave_waiting_time=self.record_sum_waiting_time/self.record_dealed_task_num
    #         ave_complet_time=self.record_sum_deal_time/self.record_dealed_task_num
    #         deadline_violation=self.record_violation_task_num/self.record_dealed_task_num
        
    #     return ave_waiting_time, ave_complet_time, deadline_violation


